﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using PaintDotNet.Effects;
using PaintDotNet.PropertySystem;
using PaintDotNet;
using System.Runtime.InteropServices;
using System.IO;
using System.Configuration;
using System.Windows.Forms;

namespace AMPEffects
{
    public abstract class AmpEffectBase : PaintDotNet.Effects.PropertyBasedEffect
    {
        private bool newRender = false;
        private bool isInitialized;
        
        public bool IsInitialized
        {
            get
            {
                return this.isInitialized;
            }
            set
            {
                this.isInitialized = value;
            }
        }

        protected AmpEffectBase(string name, Image image, string subMenuName, PaintDotNet.Effects.EffectFlags flags)
            : base(name, image, subMenuName, flags)
        {
            MaxTextureSize = 8216;
            CustomRegionHandling = false;
        }

        public int MaximumRegionWidth { get; set; }
        public int MaximumRegionHeight { get; set; }
        public int MaxTextureSize { get; private set; }
        public bool CustomRegionHandling { get; set; }

        internal static Configuration GetDllConfig()
        {
            var configFile = System.Reflection.Assembly.GetExecutingAssembly().Location + ".config";
            var map = new ExeConfigurationFileMap
            {
                ExeConfigFilename = configFile
            };
            return ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
        }
        
        protected abstract override PropertyCollection OnCreatePropertyCollection();

        protected virtual void OnPreRender(RenderArgs dstArgs, RenderArgs srcArgs)
        {
            this.isInitialized = true;
        }
        
        protected override void OnRender(Rectangle[] rois, int startIndex, int length)
        {
            if (length == 0)
                return;

            if (this.CustomRegionHandling && FullImageSelected(base.SrcArgs.Bounds))
            {
                if (this.newRender)
                {
                    this.newRender = false;
                    this.OnRenderRegion(SliceRectangles(new Rectangle[] { this.EnvironmentParameters.GetSelection(base.SrcArgs.Bounds).GetBoundsInt() }), base.DstArgs, base.SrcArgs);
                }
            }
            else
            {
                this.OnRenderRegion(SliceRectangles(rois.Skip<Rectangle>(startIndex).Take<Rectangle>(length).ToArray<Rectangle>()), base.DstArgs, base.SrcArgs);
            }
        }

        protected virtual void OnRenderRegion(Rectangle[] rois, RenderArgs dstArgs, RenderArgs srcArgs)
        {
        }

        protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs)
        {
            this.newRender = true;
            base.OnSetRenderInfo(newToken, dstArgs, srcArgs);
            this.OnPreRender(dstArgs, srcArgs);
        }

        internal Rectangle[] SliceRectangles(Rectangle[] rois)
        {
            if (rois.Length == 0 || (this.MaximumRegionHeight == 0 && this.MaximumRegionWidth == 0))
                return rois;

            // Re-slice regions
            List<Rectangle> sizedRegions = new List<Rectangle>();
            Rectangle[] rectCopy = rois;

            // Resize width
            foreach (Rectangle rect in rectCopy)
            {
                if (this.MaximumRegionWidth > 0 && rect.Width > this.MaximumRegionWidth)
                {
                    int sliceCount = (int)Math.Ceiling((double)rect.Width / (double)this.MaximumRegionWidth);

                    for (int i = 0; i < sliceCount; i++)
                    {
                        if (i < sliceCount - 1)
                        {
                            sizedRegions.Add(new Rectangle(rect.X + (this.MaximumRegionWidth * i), rect.Y, this.MaximumRegionWidth, rect.Height));
                        }
                        else
                        {
                            int remainingWidth = rect.Width - this.MaximumRegionWidth * (sliceCount - 1);
                            sizedRegions.Add(new Rectangle(rect.Right - remainingWidth, rect.Y, remainingWidth, rect.Height));
                        }
                    }
                }
                else
                {
                    sizedRegions.Add(rect);
                }
            }

            rectCopy = sizedRegions.ToArray();
            sizedRegions.Clear();

            // Resize height
            foreach (Rectangle rect in rectCopy)
            {
                if (this.MaximumRegionHeight > 0 && rect.Height > this.MaximumRegionHeight)
                {
                    int sliceCount = (int)Math.Ceiling((double)rect.Height / (double)this.MaximumRegionHeight);

                    for (int i = 0; i < sliceCount; i++)
                    {
                        if (i < sliceCount - 1)
                        {
                            sizedRegions.Add(new Rectangle(rect.X, rect.Y + (this.MaximumRegionHeight * i), rect.Width, this.MaximumRegionHeight));
                        }
                        else
                        {
                            int remainingHeight = rect.Height - this.MaximumRegionHeight * (sliceCount - 1);
                            sizedRegions.Add(new Rectangle(rect.X, rect.Bottom - remainingHeight, rect.Width, remainingHeight));
                        }
                    }
                }
                else
                {
                    sizedRegions.Add(rect);
                }
            }

            return sizedRegions.ToArray();
        }

        internal bool FullImageSelected(Rectangle bounds)
        {
            Rectangle[] rois = this.EnvironmentParameters.GetSelection(bounds).GetRegionScansReadOnlyInt();
            return (rois.Length == 1 && rois[0] == bounds);
        }

        internal static unsafe void CustomCopy(void* dest, void* src, int count)
        {
            int block;

            block = count >> 3;

            long* pDest = (long*)dest;
            long* pSrc = (long*)src;

            for (int i = 0; i < block; i++)
            {
                *pDest = *pSrc; pDest++; pSrc++;
            }

            dest = pDest;
            src = pSrc;
            count = count - (block << 3);

            if (count > 0)
            {
                byte* pDestB = (byte*)dest;
                byte* pSrcB = (byte*)src;

                for (int i = 0; i < count; i++)
                {
                    *pDestB = *pSrcB; pDestB++; pSrcB++;
                }
            }
        }
    }
}
